#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>

//条件变量
//当一个线程互斥地访问某个变量时，它可能发现在其它线程改变状态之前，它什么也做不了。
//例如一个线程访问队列时，发现队列为空，它只能等待，只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

//同步概念与竞态条件
// 同步：在保证数据安全的前提下，让线程能够按照某种特定的顺序访问临界资源，从而有效避免饥饿问题，叫做同步
// 竞态条件：指多个线程或者进程在读写一个共享数据时结果依赖于它们执行的相对时间的情形。

//条件变量相关函数
//条件变量初始化
// pthread_cond_init函数:
// 功能:
//     初始化条件变量
// 原型:
//     int phtread_cond_initt(pthread_cond_t *restrict cond,const pthread_condattr_t *restrictattr);
// 参数:
//     cond:需要进行初始化的条件变量
//     attr:需要对这个条件变量的属性进行什么设置,nullptr为默认的属性
// 返回值:
//     成功返回0,失败返回错误码

// 等待条件满足
// pthread_cond_wait函数
// 原型:
//     int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
// 功能:
//     将使用条件变量的线程进行等待
// 参数:
//     cond:需要在该条件变量上进行等待
//     mutex:如果使用mutex,需要对mutex进行解锁,再挂起自己,返回时,需要自动竞争锁，获取到锁之后，才能返回
//     如果没有使用锁,就不进行操作
// 返回值:
//     成功返回0,失败返回错误码

// 唤醒等待
// phtread_cond_signal函数
// 原型:
//     int pthread_cond_signal(pthread_con_t* cond);
// 功能:
//     将条件设为满足,唤醒等待队列里第一个的执行流
// 参数:
//     cond:满足这个条件,并唤醒使用这个条件变量的线程
// 返回值:
//     成功返回0,失败返回错误码

// phtread_cond_broadcast函数
// 原型:
//     int pthread_cond_broadcast(pthread_con_t* cond);
// 功能:
//     将条件设为满足,唤醒等待队列里所有执行流
// 参数:
//     cond:满足这个条件,并唤醒使用这个条件变量的所有线程
// 返回值:
//     成功返回0,失败返回错误码

// 条件变量销毁
// 原型:
//     int pthread_cond_destroy(pthread_con_t* cond)
// 功能:
//     销毁指定的条件变量
// 参数:
//     cond:需要被销毁的条件变量的地址
// 返回值:
//     成功返回0,失败返回错误码

//下面我们实现一个简单的通过一个线程去控制其他线程,让其定期运行

 #define NUM 3
 pthread_mutex_t mutex;
 pthread_cond_t cond;

 void* ctrl(void* arg)
 {
     std::string name = (char*)arg;
     while(true)
     {
         sleep(1);
         //唤醒在条件变量下等待的一个线程,是哪一个?
         //条件变量里面有一个等待队列,唤醒在等待队列等待的第一线程
         pthread_cond_signal(&cond);
         //唤醒在等待队列等待的所有线程,按照等待队列里面的顺序
         //pthread_cond_broadcast(&cond);
         std::cout << "master say begin work!" << std::endl;

     }
 }

 void* work(void* arg)
 {
     int number = *(int* )arg;
     delete (int* )arg;

     while(true)
     {
         //这里的mutex不同,之后解释
         //同一个线程等待并唤醒过后,再一次等待,也要遵守规矩,来到等待队列的最后进行等待
         pthread_cond_wait(&cond, &mutex);
         std::cout << "worker : " << number << " is working......" << std::endl;

     }
 }

 int main()
 {
     pthread_mutex_init(&mutex, nullptr);
     pthread_cond_init(&cond, nullptr);

     pthread_t master;
     pthread_create(&master, nullptr, ctrl, (void* ) "master");

     pthread_t worker1;
     int* number1 = new int(1);
     pthread_create(&worker1, nullptr, work, (void*) number1);

     pthread_t worker2;
     int* number2 = new int(2);
     pthread_create(&worker2, nullptr, work, (void* )number2);

     pthread_t worker3;
     int* number3 = new int(3);
     pthread_create(&worker3, nullptr, work, (void* )number3);

     pthread_join(master, nullptr);
     pthread_join(worker1, nullptr);
     pthread_join(worker2, nullptr);
     pthread_join(worker3, nullptr);

     pthread_mutex_destroy(&mutex);
     pthread_cond_destroy(&cond);

     return 0;
 }

//为什么pthread_cond_wait需要互斥量?
// 条件等待是线程间同步的一种手段，如果只有一个线程，条件不满足，一直等下去都不会满足
// 所以必须要有一个线程通过某些操作，改变共享变量，使原先不满足的条件变得满足
// 并且友好的通知等待在条件变量上的线程。
// 条件不会无缘无故的突然变得满足了，必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。
// 没有互斥锁就无法安全的获取和修改共享数据。

// 按照上面的说法，我们设计出如下的代码：先上锁，发现条件不满足，解锁，然后等待在条件变量上不就
// 行了，如下代码 :
//     // 错误的设计
//     pthread_mutex_lock(&mutex);
// while (condition_is_false)
// {
//     pthread_mutex_unlock(&mutex);
//     //解锁之后，等待之前，条件可能已经满足，信号已经发出，但是该信号可能被错过
//     pthread_cond_wait(&cond);
//     pthread_mutex_lock(&mutex);
// }
// pthread_mutex_unlock(&mutex);

// 当我们解锁之后,有一个其他线程获取到互斥锁,然后发送了信号->
// 我们了解一下pthred_cond_wait的原理,进入该函数后,回去看条件变量等于0不
// 如果等于0(条件不成立),就把互斥量变为1(解锁),直到cond_wait返回的时候(接收到信号的时候,这个过程是挂起的)
// 把条件量改为1,把互斥量恢复为0(加锁)
// ->这个时候还没有执行到pthread_cond_wait函数内部挂起等待的那一步
// 然后这个信号就会错过,然后执行到pthread_cond_wait函数内部挂起等待的那一步
// 因为错过了信号,所以就会导致这个线程永远阻塞在phtread_cond_wait
// 所以解锁和等待必须自己控制为原子操作

